/**
 * @file   llevents_tut.cpp
 * @author Nat Goodspeed
 * @date   2008-09-12
 * @brief  Test of llevents.h
 * 
 * $LicenseInfo:firstyear=2008&license=viewergpl$
 * 
 * Copyright (c) 2008-2010, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#if LL_WINDOWS
#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
#endif

// Precompiled header
#include "linden_common.h"
// associated header
// UGLY HACK! We want to verify state internal to the classes without
// providing public accessors.
#define testable public
#include "llevents.h"
#undef testable
#include "lllistenerwrapper.h"
// STL headers
// std headers
#include <iostream>
#include <typeinfo>
// external library headers
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/assign/list_of.hpp>
// other Linden headers
#include "lltut.h"
#include "stringize.h"
#include "tests/listener.h"

using boost::assign::list_of;

template<typename T>
T make(const T& value) { return value; }

/*****************************************************************************
*   tut test group
*****************************************************************************/
namespace tut
{
    struct events_data
    {
        events_data():
            pumps(LLEventPumps::instance()),
            listener0("first"),
            listener1("second")
        {}
        LLEventPumps& pumps;
        Listener listener0;
        Listener listener1;

        void check_listener(const std::string& desc, const Listener& listener, LLSD::Integer got)
        {
            ensure_equals(STRINGIZE(listener << ' ' << desc),
                          listener.getLastEvent().asInteger(), got);
        }
    };
    typedef test_group<events_data> events_group;
    typedef events_group::object events_object;
    tut::events_group evgr("events");

    template<> template<>
    void events_object::test<1>()
    {
        set_test_name("basic operations");
        // Now there's a static constructor in llevents.cpp that registers on
        // the "mainloop" pump to call LLEventPumps::flush().
        // Actually -- having to modify this to track the statically-
        // constructed pumps in other TUT modules in this giant monolithic test
        // executable isn't such a hot idea.
//      ensure_equals("initial pump", pumps.mPumpMap.size(), 1);
        size_t initial_pumps(pumps.mPumpMap.size());
        LLEventPump& per_frame(pumps.obtain("per-frame"));
        ensure_equals("first explicit pump", pumps.mPumpMap.size(), initial_pumps+1);
        // Verify that per_frame was instantiated as an LLEventStream.
        ensure("LLEventStream leaf class", dynamic_cast<LLEventStream*>(&per_frame));
        ensure("enabled", per_frame.enabled());
        // Trivial test, but posting an event to an EventPump with no
        // listeners should not blow up. The test is relevant because defining
        // a boost::signal with a non-void return signature, using the default
        // combiner, blows up if there are no listeners. This is because the
        // default combiner is defined to return the value returned by the
        // last listener, which is meaningless if there were no listeners.
        per_frame.post(0);
        LLBoundListener connection = listener0.listenTo(per_frame);
        ensure("connected", connection.connected());
        ensure("not blocked", ! connection.blocked());
        per_frame.post(1);
        check_listener("received", listener0, 1);
        { // block the connection
            LLEventPump::Blocker block(connection);
            ensure("blocked", connection.blocked());
            per_frame.post(2);
            check_listener("not updated", listener0, 1);
        } // unblock
        ensure("unblocked", ! connection.blocked());
        per_frame.post(3);
        check_listener("unblocked", listener0, 3);
        LLBoundListener sameConnection = per_frame.getListener(listener0.getName());
        ensure("still connected", sameConnection.connected());
        ensure("still not blocked", ! sameConnection.blocked());
        { // block it again
            LLEventPump::Blocker block(sameConnection);
            ensure("re-blocked", sameConnection.blocked());
            per_frame.post(4);
            check_listener("re-blocked", listener0, 3);
        } // unblock
        bool threw = false;
        try
        {
            // NOTE: boost::bind() saves its arguments by VALUE! If you pass
            // an object instance rather than a pointer, you'll end up binding
            // to an internal copy of that instance! Use boost::ref() to
            // capture a reference instead.
            per_frame.listen(listener0.getName(), // note bug, dup name
                             boost::bind(&Listener::call, boost::ref(listener1), _1));
        }
        catch (const LLEventPump::DupListenerName& e)
        {
            threw = true;
            ensure_equals(e.what(),
                          std::string("DupListenerName: "
                                      "Attempt to register duplicate listener name '") +
                          listener0.getName() +
                          "' on " + typeid(per_frame).name() + " '" + per_frame.getName() + "'");
        }
        ensure("threw DupListenerName", threw);
        // do it right this time
        listener1.listenTo(per_frame);
        per_frame.post(5);
        check_listener("got", listener0, 5);
        check_listener("got", listener1, 5);
        per_frame.enable(false);
        per_frame.post(6);
        check_listener("didn't get", listener0, 5);
        check_listener("didn't get", listener1, 5);
        per_frame.enable();
        per_frame.post(7);
        check_listener("got", listener0, 7);
        check_listener("got", listener1, 7);
        per_frame.stopListening(listener0.getName());
        ensure("disconnected 0", ! connection.connected());
        ensure("disconnected 1", ! sameConnection.connected());
        per_frame.post(8);
        check_listener("disconnected", listener0, 7);
        check_listener("still connected", listener1, 8);
        per_frame.stopListening(listener1.getName());
        per_frame.post(9);
        check_listener("disconnected", listener1, 8);
    }

    template<> template<>
    void events_object::test<2>()
    {
        set_test_name("callstop() returning true");
        LLEventPump& per_frame(pumps.obtain("per-frame"));
        listener0.reset(0);
        listener1.reset(0);
        LLBoundListener bound0 = listener0.listenTo(per_frame, &Listener::callstop);
        LLBoundListener bound1 = listener1.listenTo(per_frame, &Listener::call,
                                                    // after listener0
                                                    make<LLEventPump::NameList>(list_of(listener0.getName())));
        ensure("enabled", per_frame.enabled());
        ensure("connected 0", bound0.connected());
        ensure("unblocked 0", ! bound0.blocked());
        ensure("connected 1", bound1.connected());
        ensure("unblocked 1", ! bound1.blocked());
        per_frame.post(1);
        check_listener("got", listener0, 1);
        // Because listener0.callstop() returns true, control never reaches listener1.call().
        check_listener("got", listener1, 0);
    }

    bool chainEvents(Listener& someListener, const LLSD& event)
    {
        // Make this call so we can watch for side effects for test purposes.
        someListener.call(event);
        // This function represents a recursive event chain -- or some other
        // scenario in which an event handler raises additional events.
        int value = event.asInteger();
        if (value)
        {
            LLEventPumps::instance().obtain("login").post(value - 1);
        }
        return false;
    }

    template<> template<>
    void events_object::test<3>()
    {
        set_test_name("LLEventQueue delayed action");
        // This access is NOT legal usage: we can do it only because we're
        // hacking private for test purposes. Normally we'd either compile in
        // a particular name, or (later) edit a config file.
        pumps.mQueueNames.insert("login");
        LLEventPump& login(pumps.obtain("login"));
        // The "mainloop" pump is special: posting on that implicitly calls
        // LLEventPumps::flush(), which in turn should flush our "login"
        // LLEventQueue.
        LLEventPump& mainloop(pumps.obtain("mainloop"));
        ensure("LLEventQueue leaf class", dynamic_cast<LLEventQueue*>(&login));
        listener0.listenTo(login);
        listener0.reset(0);
        login.post(1);
        check_listener("waiting for queued event", listener0, 0);
        mainloop.post(LLSD());
        check_listener("got queued event", listener0, 1);
        login.stopListening(listener0.getName());
        // Verify that when an event handler posts a new event on the same
        // LLEventQueue, it doesn't get processed in the same flush() call --
        // it waits until the next flush() call.
        listener0.reset(17);
        login.listen("chainEvents", boost::bind(chainEvents, boost::ref(listener0), _1));
        login.post(1);
        check_listener("chainEvents(1) not yet called", listener0, 17);
        mainloop.post(LLSD());
        check_listener("chainEvents(1) called", listener0, 1);
        mainloop.post(LLSD());
        check_listener("chainEvents(0) called", listener0, 0);
        mainloop.post(LLSD());
        check_listener("chainEvents(-1) not called", listener0, 0);
        login.stopListening("chainEvents");
    }

    template<> template<>
    void events_object::test<4>()
    {
        set_test_name("explicitly-instantiated LLEventStream");
        // Explicitly instantiate an LLEventStream, and verify that it
        // self-registers with LLEventPumps
        size_t registered = pumps.mPumpMap.size();
        size_t owned = pumps.mOurPumps.size();
        LLEventPump* localInstance;
        {
            LLEventStream myEventStream("stream");
            localInstance = &myEventStream;
            LLEventPump& stream(pumps.obtain("stream"));
            ensure("found named LLEventStream instance", &stream == localInstance);
            ensure_equals("registered new instance", pumps.mPumpMap.size(), registered + 1);
            ensure_equals("explicit instance not owned", pumps.mOurPumps.size(), owned);
        } // destroy myEventStream -- should unregister
        ensure_equals("destroyed instance unregistered", pumps.mPumpMap.size(), registered);
        ensure_equals("destroyed instance not owned", pumps.mOurPumps.size(), owned);
        LLEventPump& stream(pumps.obtain("stream"));
        ensure("new LLEventStream instance", &stream != localInstance);
        ensure_equals("obtain()ed instance registered", pumps.mPumpMap.size(), registered + 1);
        ensure_equals("obtain()ed instance owned", pumps.mOurPumps.size(), owned + 1);
    }

    template<> template<>
    void events_object::test<5>()
    {
        set_test_name("stopListening()");
        LLEventPump& login(pumps.obtain("login"));
        listener0.listenTo(login);
        login.stopListening(listener0.getName());
        // should not throw because stopListening() should have removed name
        listener0.listenTo(login, &Listener::callstop);
        LLBoundListener wrong = login.getListener("bogus");
        ensure("bogus connection disconnected", ! wrong.connected());
        ensure("bogus connection blocked", wrong.blocked());
    }

    template<> template<>
    void events_object::test<6>()
    {
        set_test_name("chaining LLEventPump instances");
        LLEventPump& upstream(pumps.obtain("upstream"));
        // One potentially-useful construct is to chain LLEventPumps together.
        // Among other things, this allows you to turn subsets of listeners on
        // and off in groups.
        LLEventPump& filter0(pumps.obtain("filter0"));
        LLEventPump& filter1(pumps.obtain("filter1"));
        upstream.listen(filter0.getName(),
                        boost::bind(&LLEventPump::post, boost::ref(filter0), _1));
        upstream.listen(filter1.getName(),
                        boost::bind(&LLEventPump::post, boost::ref(filter1), _1));
        listener0.listenTo(filter0);
        listener1.listenTo(filter1);
        listener0.reset(0);
        listener1.reset(0);
        upstream.post(1);
        check_listener("got unfiltered", listener0, 1);
        check_listener("got unfiltered", listener1, 1);
        filter0.enable(false);
        upstream.post(2);
        check_listener("didn't get filtered", listener0, 1);
        check_listener("got filtered", listener1, 2);
    }

    template<> template<>
    void events_object::test<7>()
    {
        set_test_name("listener dependency order");
        typedef LLEventPump::NameList NameList;
        typedef Collect::StringList StringList;
        LLEventPump& button(pumps.obtain("button"));
        Collect collector;
        button.listen("Mary",
                      boost::bind(&Collect::add, boost::ref(collector), "Mary", _1),
                      // state that "Mary" must come after "checked"
                      make<NameList>(list_of("checked")));
        button.listen("checked",
                      boost::bind(&Collect::add, boost::ref(collector), "checked", _1),
                      // "checked" must come after "spot"
                      make<NameList>(list_of("spot")));
        button.listen("spot",
                      boost::bind(&Collect::add, boost::ref(collector), "spot", _1));
        button.post(1);
        ensure_equals(collector.result, make<StringList>(list_of("spot")("checked")("Mary")));
        collector.clear();
        button.stopListening("Mary");
        button.listen("Mary",
                      boost::bind(&Collect::add, boost::ref(collector), "Mary", _1),
                      LLEventPump::empty, // no after dependencies
                      // now "Mary" must come before "spot"
                      make<NameList>(list_of("spot")));
        button.post(2);
        ensure_equals(collector.result, make<StringList>(list_of("Mary")("spot")("checked")));
        collector.clear();
        button.stopListening("spot");
        std::string threw;
        try
        {
            button.listen("spot",
                          boost::bind(&Collect::add, boost::ref(collector), "spot", _1),
                          // after "Mary" and "checked" -- whoops!
                          make<NameList>(list_of("Mary")("checked")));
        }
        catch (const LLEventPump::Cycle& e)
        {
            threw = e.what();
//          std::cout << "Caught: " << e.what() << '\n';
        }
        // Obviously the specific wording of the exception text can
        // change; go ahead and change the test to match.
        // Establish that it contains:
        // - the name and runtime type of the LLEventPump
        ensure_contains("LLEventPump type", threw, typeid(button).name());
        ensure_contains("LLEventPump name", threw, "'button'");
        // - the name of the new listener that caused the problem
        ensure_contains("new listener name", threw, "'spot'");
        // - a synopsis of the problematic dependencies.
        ensure_contains("cyclic dependencies", threw,
                        "\"Mary\" -> before (\"spot\")");
        ensure_contains("cyclic dependencies", threw,
                        "after (\"spot\") -> \"checked\"");
        ensure_contains("cyclic dependencies", threw,
                        "after (\"Mary\", \"checked\") -> \"spot\"");
        button.listen("yellow",
                      boost::bind(&Collect::add, boost::ref(collector), "yellow", _1),
                      make<NameList>(list_of("checked")));
        button.listen("shoelaces",
                      boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1),
                      make<NameList>(list_of("checked")));
        button.post(3);
        ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
        collector.clear();
        threw.clear();
        try
        {
            button.listen("of",
                          boost::bind(&Collect::add, boost::ref(collector), "of", _1),
                          make<NameList>(list_of("shoelaces")),
                          make<NameList>(list_of("yellow")));
        }
        catch (const LLEventPump::OrderChange& e)
        {
            threw = e.what();
//          std::cout << "Caught: " << e.what() << '\n';
        }
        // Same remarks about the specific wording of the exception. Just
        // ensure that it contains enough information to clarify the
        // problem and what must be done to resolve it.
        ensure_contains("LLEventPump type", threw, typeid(button).name());
        ensure_contains("LLEventPump name", threw, "'button'");
        ensure_contains("new listener name", threw, "'of'");
        ensure_contains("prev listener name", threw, "'yellow'");
        ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces");
        ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow");
        button.post(4);
        ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
    }

    template<> template<>
    void events_object::test<8>()
    {
        set_test_name("tweaked and untweaked LLEventPump instance names");
        {   // nested scope
            // Hand-instantiate an LLEventStream...
            LLEventStream bob("bob");
            bool threw = false;
            try
            {
                // then another with a duplicate name.
                LLEventStream bob2("bob");
            }
            catch (const LLEventPump::DupPumpName& /*e*/)
            {
                threw = true;
//              std::cout << "Caught: " << e.what() << '\n';
            }
            ensure("Caught DupPumpName", threw);
        }   // delete first 'bob'
        LLEventStream bob("bob");   // should work, previous one unregistered
        LLEventStream bob1("bob", true); // allowed to tweak name
        ensure_equals("tweaked LLEventStream name", bob1.getName(), "bob1");
        std::vector< boost::shared_ptr<LLEventStream> > streams;
        for (int i = 2; i <= 10; ++i)
        {
            streams.push_back(boost::shared_ptr<LLEventStream>(new LLEventStream("bob", true)));
        }
        ensure_equals("last tweaked LLEventStream name", streams.back()->getName(), "bob10");
    }

    // Define a function that accepts an LLListenerOrPumpName
    void eventSource(const LLListenerOrPumpName& listener)
    {
        // Pretend that some time has elapsed. Call listener immediately.
        listener(17);
    }

    template<> template<>
    void events_object::test<9>()
    {
        set_test_name("LLListenerOrPumpName");
        // Passing a boost::bind() expression to LLListenerOrPumpName
        listener0.reset(0);
        eventSource(boost::bind(&Listener::call, boost::ref(listener0), _1));
        check_listener("got by listener", listener0, 17);
        // Passing a string LLEventPump name to LLListenerOrPumpName
        listener0.reset(0);
        LLEventStream random("random");
        listener0.listenTo(random);
        eventSource("random");
        check_listener("got by pump name", listener0, 17);
        bool threw = false;
        try
        {
            LLListenerOrPumpName empty;
            empty(17);
        }
        catch (const LLListenerOrPumpName::Empty&)
        {
            threw = true;
        }
        ensure("threw Empty", threw);
    }

    class TempListener: public Listener
    {
    public:
        TempListener(const std::string& name, bool& liveFlag):
            Listener(name),
            mLiveFlag(liveFlag)
        {
            mLiveFlag = true;
        }

        virtual ~TempListener()
        {
            mLiveFlag = false;
        }

    private:
        bool& mLiveFlag;
    };

    template<> template<>
    void events_object::test<10>()
    {
        set_test_name("listen(boost::bind(...TempListener...))");
        // listen() can't do anything about a plain TempListener instance:
        // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass
        bool live = false;
        LLEventPump& heaptest(pumps.obtain("heaptest"));
        LLBoundListener connection;
        {
            TempListener tempListener("temp", live);
            ensure("TempListener constructed", live);
            connection = heaptest.listen(tempListener.getName(),
                                         boost::bind(&Listener::call,
                                                     boost::ref(tempListener),
                                                     _1));
            heaptest.post(1);
            check_listener("received", tempListener, 1);
        } // presumably this will make newListener go away?
        // verify that
        ensure("TempListener destroyed", ! live);
        // This is the case against which we can't defend. Don't even try to
        // post to heaptest -- that would engage Undefined Behavior.
        // Cautiously inspect connection...
        ensure("misleadingly connected", connection.connected());
        // then disconnect by hand.
        heaptest.stopListening("temp");
    }

    template<> template<>
    void events_object::test<11>()
    {
        set_test_name("listen(boost::bind(...weak_ptr...))");
        // listen() detecting weak_ptr<TempListener> in boost::bind() object
        bool live = false;
        LLEventPump& heaptest(pumps.obtain("heaptest"));
        LLBoundListener connection;
        ensure("default state", ! connection.connected());
        {
            boost::shared_ptr<TempListener> newListener(new TempListener("heap", live));
            newListener->reset();
            ensure("TempListener constructed", live);
            connection = heaptest.listen(newListener->getName(),
                                         boost::bind(&Listener::call, weaken(newListener), _1));
            ensure("new connection", connection.connected());
            heaptest.post(1);
            check_listener("received", *newListener, 1);
        } // presumably this will make newListener go away?
        // verify that
        ensure("TempListener destroyed", ! live);
        ensure("implicit disconnect", ! connection.connected());
        // now just make sure we don't blow up trying to access a freed object!
        heaptest.post(2);
    }

    template<> template<>
    void events_object::test<12>()
    {
        set_test_name("listen(boost::bind(...shared_ptr...))");
/*==========================================================================*|
        // DISABLED because I've made this case produce a compile error.
        // Following the error leads the disappointed dev to a comment
        // instructing her to use the weaken() function to bind a weak_ptr<T>
        // instead of binding a shared_ptr<T>, and explaining why. I know of
        // no way to use TUT to code a repeatable test in which the expected
        // outcome is a compile error. The interested reader is invited to
        // uncomment this block and build to see for herself.

        // listen() detecting shared_ptr<TempListener> in boost::bind() object
        bool live = false;
        LLEventPump& heaptest(pumps.obtain("heaptest"));
        LLBoundListener connection;
        std::string listenerName("heap");
        ensure("default state", ! connection.connected());
        {
            boost::shared_ptr<TempListener> newListener(new TempListener(listenerName, live));
            ensure_equals("use_count", newListener.use_count(), 1);
            newListener->reset();
            ensure("TempListener constructed", live);
            connection = heaptest.listen(newListener->getName(),
                                         boost::bind(&Listener::call, newListener, _1));
            ensure("new connection", connection.connected());
            ensure_equals("use_count", newListener.use_count(), 2);
            heaptest.post(1);
            check_listener("received", *newListener, 1);
        } // this should make newListener go away...
        // Unfortunately, the fact that we've bound a shared_ptr by value into
        // our LLEventPump means that copy will keep the referenced object alive.
        ensure("TempListener still alive", live);
        ensure("still connected", connection.connected());
        // disconnecting explicitly should delete the TempListener...
        heaptest.stopListening(listenerName);
#if 0   // however, in my experience, it does not. I don't know why not.
        // Ah: on 2009-02-19, Frank Mori Hess, author of the Boost.Signals2
        // library, stated on the boost-users mailing list:
        // http://www.nabble.com/Re%3A--signals2--review--The-review-of-the-signals2-library-(formerly-thread_safe_signals)-begins-today%2C-Nov-1st-p22102367.html
        // "It will get destroyed eventually. The signal cleans up its slot
        // list little by little during connect/invoke. It doesn't immediately
        // remove disconnected slots from the slot list since other threads
        // might be using the same slot list concurrently. It might be
        // possible to make it immediately reset the shared_ptr owning the
        // slot though, leaving an empty shared_ptr in the slot list, since
        // that wouldn't invalidate any iterators."
        ensure("TempListener destroyed", ! live);
        ensure("implicit disconnect", ! connection.connected());
#endif  // 0
        // now just make sure we don't blow up trying to access a freed object!
        heaptest.post(2);
|*==========================================================================*/
    }

    class TempTrackableListener: public TempListener, public LLEventTrackable
    {
    public:
        TempTrackableListener(const std::string& name, bool& liveFlag):
            TempListener(name, liveFlag)
        {}
    };

    template<> template<>
    void events_object::test<13>()
    {
        set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
        bool live = false;
        LLEventPump& heaptest(pumps.obtain("heaptest"));
        LLBoundListener connection;
        {
            TempTrackableListener tempListener("temp", live);
            ensure("TempTrackableListener constructed", live);
            connection = heaptest.listen(tempListener.getName(),
                                         boost::bind(&TempTrackableListener::call,
                                                     boost::ref(tempListener), _1));
            heaptest.post(1);
            check_listener("received", tempListener, 1);
        } // presumably this will make tempListener go away?
        // verify that
        ensure("TempTrackableListener destroyed", ! live);
        ensure("implicit disconnect", ! connection.connected());
        // now just make sure we don't blow up trying to access a freed object!
        heaptest.post(2);
    }

    template<> template<>
    void events_object::test<14>()
    {
        set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
        bool live = false;
        LLEventPump& heaptest(pumps.obtain("heaptest"));
        LLBoundListener connection;
        {
            TempTrackableListener* newListener(new TempTrackableListener("temp", live));
            ensure("TempTrackableListener constructed", live);
            connection = heaptest.listen(newListener->getName(),
                                         boost::bind(&TempTrackableListener::call,
                                                     newListener, _1));
            heaptest.post(1);
            check_listener("received", *newListener, 1);
            // explicitly destroy newListener
            delete newListener;
        }
        // verify that
        ensure("TempTrackableListener destroyed", ! live);
        ensure("implicit disconnect", ! connection.connected());
        // now just make sure we don't blow up trying to access a freed object!
        heaptest.post(2);
    }

    template<> template<>
    void events_object::test<15>()
    {
        // This test ensures that using an LLListenerWrapper subclass doesn't
        // block Boost.Signals2 from recognizing a bound LLEventTrackable
        // subclass.
        set_test_name("listen(llwrap<LLLogListener>(boost::bind(...TempTrackableListener ref...)))");
        bool live = false;
        LLEventPump& heaptest(pumps.obtain("heaptest"));
        LLBoundListener connection;
        {
            TempTrackableListener tempListener("temp", live);
            ensure("TempTrackableListener constructed", live);
            connection = heaptest.listen(tempListener.getName(),
                                         llwrap<LLLogListener>(
                                         boost::bind(&TempTrackableListener::call,
                                                     boost::ref(tempListener), _1)));
            heaptest.post(1);
            check_listener("received", tempListener, 1);
        } // presumably this will make tempListener go away?
        // verify that
        ensure("TempTrackableListener destroyed", ! live);
        ensure("implicit disconnect", ! connection.connected());
        // now just make sure we don't blow up trying to access a freed object!
        heaptest.post(2);
    }

    class TempSharedListener: public TempListener,
                              public boost::enable_shared_from_this<TempSharedListener>
    {
    public:
        TempSharedListener(const std::string& name, bool& liveFlag):
            TempListener(name, liveFlag)
        {}
    };

    template<> template<>
    void events_object::test<16>()
    {
        set_test_name("listen(boost::bind(...TempSharedListener ref...))");
#if 0
        bool live = false;
        LLEventPump& heaptest(pumps.obtain("heaptest"));
        LLBoundListener connection;
        {
            // We MUST have at least one shared_ptr to an
            // enable_shared_from_this subclass object before
            // shared_from_this() can work.
            boost::shared_ptr<TempSharedListener>
                tempListener(new TempSharedListener("temp", live));
            ensure("TempSharedListener constructed", live);
            // However, we're not passing either the shared_ptr or its
            // corresponding weak_ptr -- instead, we're passing a reference to
            // the TempSharedListener.
/*==========================================================================*|
            std::cout << "Capturing const ref" << std::endl;
            const boost::enable_shared_from_this<TempSharedListener>& cref(*tempListener);
            std::cout << "Capturing const ptr" << std::endl;
            const boost::enable_shared_from_this<TempSharedListener>* cp(&cref);
            std::cout << "Capturing non-const ptr" << std::endl;
            boost::enable_shared_from_this<TempSharedListener>* p(const_cast<boost::enable_shared_from_this<TempSharedListener>*>(cp));
            std::cout << "Capturing shared_from_this()" << std::endl;
            boost::shared_ptr<TempSharedListener> sp(p->shared_from_this());
            std::cout << "Capturing weak_ptr" << std::endl;
            boost::weak_ptr<TempSharedListener> wp(weaken(sp));
            std::cout << "Binding weak_ptr" << std::endl;
|*==========================================================================*/
            connection = heaptest.listen(tempListener->getName(),
                                         boost::bind(&TempSharedListener::call, *tempListener, _1));
            heaptest.post(1);
            check_listener("received", *tempListener, 1);
        } // presumably this will make tempListener go away?
        // verify that
        ensure("TempSharedListener destroyed", ! live);
        ensure("implicit disconnect", ! connection.connected());
        // now just make sure we don't blow up trying to access a freed object!
        heaptest.post(2);
#endif // 0
    }
} // namespace tut
